package com.ocean.core.util.upload;

import com.ocean.core.exception.verify.ApplicationException;
import com.ocean.core.model.vo.FileUploadVo;
import com.ocean.core.util.constants.ConstantsAttribute;
import com.ocean.core.util.upload.properties.FileProperties;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

/**
 * 文件上传工具类
 *
 * @author zhang
 * @since 2022/3/4
 */
@Slf4j
@Component
public class FileUtil {

    private static FileProperties fileProperties;

    public FileUtil(FileProperties fileProperties) {
        FileUtil.fileProperties = fileProperties;
    }

    /**
     * 文件上传
     *
     * @param file 文件
     * @return 结果
     */
    public static FileUploadVo upload(MultipartFile file) {
        FileUploadVo uploadVo = new FileUploadVo();
        if (file.isEmpty()) {
            log.warn("上传文件为空，跳过...");
            throw new ApplicationException("上传文件为空");
        }
        String rename = file.getOriginalFilename();
        if (rename == null) {
            throw new ApplicationException("请上传一个有名字的文件");
        }
        LocalDate now = LocalDate.now();
        // 转换时间格式
        String format = DateTimeFormatter.ofPattern("yyyy-MM-dd").format(now);
        // 上传到指定磁盘目录
        String diskLocation = fileProperties.getUpload() + File.separator + format + File.separator;
        String sourceFolder = File.separator + format + File.separator;
        // 后缀
        String suffix = rename.substring(rename.lastIndexOf("."));
        // 完整文件名
        String fileName = getUuid() + suffix;
        // 最终目录
        String lastFilePath = diskLocation + fileName;
        // 可访问的路径
        String address = fileProperties.getRequestUrl() + sourceFolder + fileName;
        File filePath = FileUtil.createFilePath(lastFilePath);
        log.info("文件完整路径: " + filePath);
        try {
            //上传还是直接上传在容器中
            file.transferTo(filePath);
            uploadVo.setRename(rename);
            uploadVo.setOldFilePath(filePath.toString());
            uploadVo.setRequestUrl(address.replaceAll("\\\\", "/"));
        } catch (IOException e) {
            log.error("文件上传出现错误：{}", e.getMessage());
        }
        return uploadVo;
    }

    /**
     * 图片预览
     *
     * @param date     存放的日期文件夹
     * @param image    图片地址
     * @param response response
     */
    public static void view(String date, String image, HttpServletResponse response) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            String filePath = fileProperties.getUpload() + File.separator + date + File.separator + image;
            File file = new File(filePath);
            if (!file.exists()) {
                log.error("文件不存在: {}", image);
            }
            response.setContentType("application/force-download");
            response.addHeader("Content-Disposition", "attachment;fileName=" + new String(file.getName().getBytes(
                    StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            inputStream = new BufferedInputStream(new FileInputStream(filePath));
            outputStream = response.getOutputStream();
            byte[] buf = new byte[1024];
            int len;
            while ((len = inputStream.read(buf)) > 0) {
                outputStream.write(buf, 0, len);
            }
            response.flushBuffer();
        } catch (IOException e) {
            log.error("预览文件失败: {}", e.getMessage());
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * 视频预览
     * <p>
     * 应使用更安全的 IOceanVideoService.viewVideo
     *
     * @param date     日期
     * @param video    视频名称
     * @param request  request
     * @param response response
     */
    @Deprecated
    public static void viewVideo(String date, String video, HttpServletRequest request, HttpServletResponse response) {

        RandomAccessFile targetFile = null;
        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            response.reset();
            //获取请求头中Range的值
            String rangeString = request.getHeader(HttpHeaders.RANGE);

            String filePath = fileProperties.getUpload() + File.separator + date + File.separator + video;
            //打开文件
            File file = new File(filePath);
            if (file.exists()) {
                //使用RandomAccessFile读取文件
                targetFile = new RandomAccessFile(file, "r");
                long fileLength = targetFile.length();
                long requestSize = (int) fileLength;
                //分段下载视频
                if (StringUtils.hasText(rangeString)) {
                    //从Range中提取需要获取数据的开始和结束位置
                    long requestStart = 0, requestEnd = 0;
                    String[] ranges = rangeString.split("=");
                    if (ranges.length > 1) {
                        String[] rangeDatas = ranges[1].split("-");
                        requestStart = Integer.parseInt(rangeDatas[0]);
                        if (rangeDatas.length > 1) {
                            requestEnd = Integer.parseInt(rangeDatas[1]);
                        }
                    }
                    if (requestEnd != 0 && requestEnd > requestStart) {
                        requestSize = requestEnd - requestStart + 1;
                    }
                    //根据协议设置请求头
                    response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
                    response.setHeader(HttpHeaders.CONTENT_TYPE, "video/mp4");
                    if (!StringUtils.hasText(rangeString)) {
                        response.setHeader(HttpHeaders.CONTENT_LENGTH, fileLength + "");
                    } else {
                        long length;
                        if (requestEnd > 0) {
                            length = requestEnd - requestStart + 1;
                            response.setHeader(HttpHeaders.CONTENT_LENGTH, "" + length);
                            response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + requestEnd + "/" + fileLength);
                        } else {
                            length = fileLength - requestStart;
                            response.setHeader(HttpHeaders.CONTENT_LENGTH, "" + length);
                            response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + (fileLength - 1) + "/"
                                    + fileLength);
                        }
                    }
                    //断点传输下载视频返回206
                    response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                    //设置targetFile，从自定义位置开始读取数据
                    targetFile.seek(requestStart);
                } else {
                    //如果Range为空则下载整个视频
                    response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + video);
                    //设置文件长度
                    response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileLength));
                }

                //从磁盘读取数据流返回
                byte[] cache = new byte[4096];
                try {
                    while (requestSize > 0) {
                        int len = targetFile.read(cache);
                        if (requestSize < cache.length) {
                            outputStream.write(cache, 0, (int) requestSize);
                        } else {
                            outputStream.write(cache, 0, len);
                            if (len < cache.length) {
                                break;
                            }
                        }
                        requestSize -= cache.length;
                    }
                } catch (IOException e) {
                    // tomcat原话。写操作IO异常几乎总是由于客户端主动关闭连接导致，所以直接吃掉异常打日志
                    //比如使用video播放视频时经常会发送Range为0- 的范围只是为了获取视频大小，之后就中断连接了
                    log.info(e.getMessage());
                }
            } else {
                throw new RuntimeException("文件路径有误");
            }
            outputStream.flush();
        } catch (Exception e) {
            //很奇怪的问题，但不影响使用 java.io.IOException: 你的主机中的软件中止了一个已建立的连接
            //发生在拖动进度条时
//            log.error("文件传输错误", e);
//            throw new RuntimeException("文件传输错误");
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    //同上
//                    log.error("流释放错误", e);
                }
            }
            if (targetFile != null) {
                try {
                    targetFile.close();
                } catch (IOException e) {
                    log.error("文件流释放错误", e);
                }
            }
        }
    }


    /**
     * 创建目录
     *
     * @param path 文件路径
     * @return void
     * @author Chia-Hung Yeh
     * @date 14:53 2021/11/1
     **/
    public static File createFilePath(String path) {
        File file = new File(path);
        // 如果文件不存在，则创建文件
        if (file.exists() || file.getParentFile().exists()) {
            return file;
        }
        boolean mkdirs = path.contains(".") ? file.getParentFile().mkdirs() : file.mkdirs();
        if (!mkdirs) {
            log.error("创建文件夹失败: {}", path.contains(".") ? file.getParentFile() : file);
            throw new ApplicationException("创建文件夹时发生错误！");
        }
        return file;
    }

    /**
     * 截取视频帧
     *
     * @param videoPath 视频路径
     * @param numFrames 帧
     */
    @Deprecated
    public static void extractFrames(String videoPath, int numFrames, HttpServletResponse response) {
        try {
            response.setContentType("image/png");
            response.setHeader("Content-Disposition", "inline");

            FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoPath);
            grabber.start();

            OutputStream outputStream = response.getOutputStream();
            Frame frame;
            Java2DFrameConverter converter = new Java2DFrameConverter();
            for (int i = 0; i < numFrames; i++) {
                frame = grabber.grabImage();
                BufferedImage image = converter.getBufferedImage(frame);
                ImageIO.write(image, "png", outputStream);
            }
            grabber.stop();
            outputStream.flush();
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
            throw new ApplicationException(ConstantsAttribute.ERROR);
        }
    }

    public static String getUuid() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
}
